using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Lync.Widget.Library.ServiceHelpers.ServiceHelpers;
using log4net;

namespace Lync.Widget.Library.InstantMessaging
{
    public class UCInstantMessageRouter : UCInstantMessageRouterBase
	{
		private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		private static UCInstantMessageRouter _instance;		
		private List<UCInstantMessageProvider> _providers = new List<UCInstantMessageProvider>();
		private List<MessageQueue> _messageQueues = new List<MessageQueue>();

		public static UCInstantMessageRouter Instance
		{
			get
			{
				if (_instance == null)
				{
					_instance = new UCInstantMessageRouter();
				}
				return _instance;
			}
		}

		public void InitChatSession(string session, string employeeSip, string username, string page, string title, string topic)
		{

			//check if session does not exist yet.
			if (QueueExists(session))
			{
				//throw an error because this could lead to a conversation hack
				Log.Error(string.Format("cache for {0} was being initiated but it already exists.", session));
				throw new InvalidOperationException(string.Format("cache for {0} was being initiated but it already exists.", session));
			}

			var queue = new MessageQueue(session);
			_messageQueues.Add(queue);

			//check if session does not exist yet.
			if (SessionProviderExists(session))
			{
				//throw an error because this could lead to a conversation hack
				Log.Error(string.Format("session {0} was being initiated but it already exists.", session));
				throw new InvalidOperationException(string.Format("session {0} was being initiated but it already exists.", session));
			}

			//create provider for the session
            var provider = new UCInstantMessageProvider(CollabPlatform, session, employeeSip, username, page, title, topic);
			provider.ChatRequestAcceptanceChanged += provider_ChatRequestAcceptanceChanged;
			provider.ChatMessageReceivedFromLyncCLient += provider_ChatMessageReceivedFromLyncClient;
			provider.InstantMessageProviderExceptionOccured += provider_InstantMessageProviderExceptionOccured;
            provider.Notification += provider_Notification;
			provider.ChatClosed += provider_ChatClosed;
			provider.ComposingStateChanged += provider_ComposingStateChanged;
			
			//add provider to provider collection
			_providers.Add(provider);
		}

		void provider_ComposingStateChanged(object sender, ComposingStateChangedEventArgs e)
		{
			var provider = (UCInstantMessageProvider)sender;
			QueueMessage(provider.Session, new Message(e.composingState.ToString(), MessageType.ComposingStateChanged));
		}

		void DisposeProvider(UCInstantMessageProvider provider)
		{
			provider.ChatRequestAcceptanceChanged -= provider_ChatRequestAcceptanceChanged;
			provider.ChatMessageReceivedFromLyncCLient -= provider_ChatMessageReceivedFromLyncClient;
			provider.InstantMessageProviderExceptionOccured -= provider_InstantMessageProviderExceptionOccured;
			provider.Notification -= provider_Notification;
			provider.ChatClosed -= provider_ChatClosed;
			provider.ComposingStateChanged -= provider_ComposingStateChanged;
			
			provider.Dispose();
			_providers.Remove(provider);
		}

		void provider_ChatClosed(object sender, EventArgs e)
		{
			var provider = (UCInstantMessageProvider)sender;
			QueueMessage(provider.Session, new Message("Sorry, this user is no longer available for chat. You can send an email or place a call instead.", MessageType.ChatClosed));
			DisposeProvider(provider);
		}

        /// <summary>
        /// Occurs when the provider sends a notification to pass to the end user
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void provider_Notification(object sender, NotificationEventArgs e)
        {
			var provider = (UCInstantMessageProvider)sender;
			QueueMessage(provider.Session, new Message(e.message, MessageType.ServiceNotification));
        }

		/// <summary>
		/// Occurs when the provider notifies the router of an internal exception.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void provider_InstantMessageProviderExceptionOccured(object sender, InstantMessageProviderExceptionOccuredEventArgs e)
		{
			var provider = (UCInstantMessageProvider) sender;
			QueueMessage(provider.Session, new Message(e.exception.Message, MessageType.ServiceError));
		}

		/// <summary>
		/// Occurs when the provider notifies the router that a chat message was received from the lync client
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void provider_ChatMessageReceivedFromLyncClient(object sender, MessageReceivedFromLyncClientEventArgs e)
		{
			var provider = (UCInstantMessageProvider)sender;
			QueueMessage(provider.Session, new Message(e.message, MessageType.InstantMessage));
		}

		/// <summary>
		/// Occurs when the provider notifies the router that a lync client has accepted a chat request
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void provider_ChatRequestAcceptanceChanged(object sender, ChatRequestAcceptanceChangedEventArgs e)
		{
			var provider = (UCInstantMessageProvider) sender;
			if (e.isAccepted)
			{
				QueueMessage(provider.Session, new Message("Chat request accepted by the user! Go ahead and say something.", MessageType.ChatRequestAccepted));
			}
			else
			{
				QueueMessage(provider.Session, new Message("Sorry, this user is no longer available for chat. You can send an email or place a call instead.", MessageType.ChatRequestDenied));
				DisposeProvider(provider);
			}
		}

		void QueueMessage(string session, Message message)
		{
			MessageQueue cache;
			try
			{
				cache = Getcache(session);
			}
			catch (QueueNotFoundException cacheNotFoundException)
			{
				Log.WarnFormat("QueueNotFoundException for session {0} and message '{1}'", session, message);
				throw;
			}

			cache.Add(message);
		}

		public void SendInstantMessageToLyncClient(string session, string message)
		{
			UCInstantMessageProvider provider;
			try
			{
				provider = GetProvider(session);
			}
			catch (SessionNotFoundException sessionNotFoundException)
			{
				Log.WarnFormat("SessionNotFoundException for session {0} and message '{1}'", session, message);
				throw;
			}

			provider.SendInstantMessage(message);
		}

		public List<Message> GetInstantMessagesFromLynClient(string session)
		{
			MessageQueue cache;
			try
			{
				cache = Getcache(session);
			}
			catch (QueueNotFoundException cacheNotFoundException)
			{
				Log.WarnFormat("QueueNotFoundException for session {0}", session);
				throw;
			}

			return cache.GetAllAndDelete();
		}

		private UCInstantMessageProvider GetProvider(string session)
		{
			var provider = _providers.SingleOrDefault(p => p.Session.Equals(session));
			if (provider != null)
			{
				return provider;
			}
			throw new SessionNotFoundException();
		}

		private MessageQueue Getcache(string session)
		{
			var cache = _messageQueues.SingleOrDefault(p => p.Session.Equals(session));
			if (cache != null)
			{
				return cache;
			}
			throw new QueueNotFoundException();
		}

		private bool SessionProviderExists(string session)
		{
			var provider = _providers.SingleOrDefault(p => p.Session.Equals(session));
			return provider != null;
		}

		private bool QueueExists(string session)
		{
			var cache = _messageQueues.SingleOrDefault(p => p.Session.Equals(session));
			return cache != null;
		}

		public StatusReport GetStatusReport()
		{
			return new StatusReport
			                   	{
			                   		NumberOfMessageQueues = _messageQueues.Count,
									TotalNumberOfMessages = _messageQueues.Sum(queue => queue.GetMessageCount()),
									NumberOfProviders = _providers.Count,
			                   	};
		}
	}
}